package com.china.cnlongdb.server.operator.insert;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.china.cnlongdb.basic.config.LongDbConfig;
import com.china.cnlongdb.basic.constant.ConcurrentDataMap;
import com.china.cnlongdb.basic.constant.LongDbConstant;
import com.china.cnlongdb.basic.entity.database.LongTableEntity;
import com.china.cnlongdb.basic.entity.insert.InsertDataEntity;
import com.china.cnlongdb.basic.entity.matchingrule.MatchingDataTime;
import com.china.cnlongdb.basic.entity.matchingrule.MatchingField;
import com.china.cnlongdb.basic.utils.FieldHandlerUtils;
import com.china.cnlongdb.basic.utils.GenerateSequenceUtils;
import com.china.cnlongdb.basic.utils.LongFileOperator;
import com.china.cnlongdb.basic.utils.RandomUtil;
import com.china.cnlongdb.server.config.LongdbConfigHandler;
import com.china.cnlongdb.server.connect.LongDbConnect;
import com.china.cnlongdb.server.datasync.LongdbMasterSalveDataSync;

/**
 * 系统名称：InsertOperator.java
 * 模块名称：
 * 模块描述：longdb - 插入操作服务实现层
 * 功能列表：
 * 模块作者：ZOUYONG
 * 开发时间：2018年11月11日 上午8:56:32
 * 模块路径：com.china.cnlongdb.server.insert.InsertOperator
 * 更新记录：
 */
public class InsertOperator {

	private final Logger logger = (Logger) LoggerFactory.getLogger(InsertOperator.class);
	
	// 数据库连接对象
	private LongDbConnect connect;
	public InsertOperator(LongDbConnect connect) {
		logger.debug(LongDbConstant.longdbDefaultName + "初始化[新增执行器]...");
		this.connect = connect;
	}

	/**
	 * 功能描述：将数据插入表中
	 * 模块作者：ZOUYONG
	 * 开发时间：2018年11月11日 上午8:58:02
	 * 更新记录：
	 * 返回数据：boolean
	 */
	public boolean insert(Object object, String tableName, boolean isynchronized) {
		// 创建表，获取表的配置信息
		LongTableEntity table = connect.getCreateOperator().createTable(tableName);
		// 获取当前的事务名称
		String transactionName = Thread.currentThread().getName();
		try {
			// 初始默认的longId
			String insertKey = GenerateSequenceUtils.generateSequenceLongId();
			String currDateStr = LongDbConstant.generFormatThree.format(new Date());
			String[] dateAry = currDateStr.split("-");
			
			String absolutePath = connect.getDataBaseRootDirectory() + table.getFilePath();
			Long insertTime = Long.valueOf(dateAry[0] + dateAry[1] + dateAry[2]);

			InsertDataEntity insertData = new InsertDataEntity();
			insertData.setInsertKey(insertKey);
			insertData.setInsertTime(insertTime);
			insertData.setAbsolutePath(absolutePath);
			insertData.setJsonStr(JSON.toJSONString(object));
			insertData.setTableName(tableName);
			
			// 是否需要事务
			if(StringUtils.isNotBlank(transactionName) && transactionName.endsWith(LongDbConstant.longdbTranscationFlag)) {
				ConcurrentHashMap<String, InsertDataEntity> insertDataMap = ConcurrentDataMap.tempInsertDataMap.get(transactionName);
				if(null == insertDataMap) {
					insertDataMap = new ConcurrentHashMap<String, InsertDataEntity>();
				}
				insertDataMap.put(insertKey, insertData);
				ConcurrentDataMap.tempInsertDataMap.put(transactionName, insertDataMap);
			}
			// 不需要事务
			else {
				// 异步插入数据，批量插入时使用
				if(isynchronized) {
					ConcurrentDataMap.insertDataMap.put(insertKey, insertData);
				}
				// 同步插入数据
				else {
					// 单个插入数据
					singleInsertDataWriteTable(insertData);
				}
			}
		} catch(Exception e) {
			e.printStackTrace();
			throw new RuntimeException("保存数据操作异常："+e.getMessage(), e);
		}
		return true;
	}

	/**
	 * 功能描述：插入一条数据
	 × 模块作者：ZOUYONG
	 × 开发时间：2018年12月26日 下午4:17:06
	 × 返回数据：void
	 */
	public void singleInsertDataWriteTable(InsertDataEntity insertData) {

		Map<String, File> availableTableMap = new HashMap<String, File>();
		Map<String, StringBuffer> tableContentMap = new HashMap<String, StringBuffer>();
		try {
			if(!handSingleInsertData(insertData, availableTableMap, tableContentMap, false)) {
				throw new RuntimeException("插入数据异常！");
			}
		} finally {
			// 写入文件-插入数据完成后，清空map
			handlerFileContent(tableContentMap, availableTableMap, null);
			availableTableMap.clear(); tableContentMap.clear();
		}
		
		// 处理主从数据同步
		LongdbMasterSalveDataSync.masterSlaveSyncData(connect, LongDbConstant.longdbActuatorInsert, "singleInsertDataWriteTable", insertData);
	}

	/**
	 * 功能描述：批量新增数据
	 × 模块作者：ZOUYONG
	 × 开发时间：2018年12月7日 下午1:17:27
	 × 返回数据：Integer
	 */
	public Integer batchInsertDataWriteTable(Map<String, InsertDataEntity> concurrentHashMap) {
		
		Integer result = 0;
		if(null == concurrentHashMap || concurrentHashMap.size() == 0) {
			return result;
		}

		String currDateTimeStr = LongDbConstant.generFormatTwo.format(new Date());
		Long insertTime = Long.valueOf(currDateTimeStr);
		Map<String, File> availableTableMap = new HashMap<String, File>();
		Map<String, StringBuffer> tableContentMap = new HashMap<String, StringBuffer>();
		Map<String, InsertDataEntity> insertMap = new HashMap<String, InsertDataEntity>();
		
		try {
			for(Iterator<String> iter = concurrentHashMap.keySet().iterator(); iter.hasNext(); ) {
				String key = iter.next();
				InsertDataEntity insertData = concurrentHashMap.get(key);
				concurrentHashMap.remove(key);
				if(null == insertData) {
					continue;
				}
				if(insertData.getInsertTime() > insertTime) {
					continue;
				}
				if(!handSingleInsertData(insertData, availableTableMap, tableContentMap, true)) {
					continue;
				}
				insertMap.put(key, insertData);
				result++;
			}
		} finally {
			// 写入文件-插入数据完成后，清空map
			handlerFileContent(tableContentMap, availableTableMap, null);
			availableTableMap.clear(); tableContentMap.clear();
		}

		// 处理主从数据同步
		LongdbMasterSalveDataSync.masterSlaveSyncData(connect, LongDbConstant.longdbActuatorInsert, "batchInsertDataWriteTable", insertMap);
		return result;
	}

	/**
	 * 功能描述：累加要插入的数据
	 × 模块作者：ZOUYONG
	 × 开发时间：2018年12月26日 下午4:39:59
	 × 返回数据：boolean
	 */
	@SuppressWarnings("unchecked")
	private boolean handSingleInsertData(InsertDataEntity insertData, 
			Map<String, File> availableTableMap, Map<String, StringBuffer> tableContentMap, boolean status) {
		
		String tableName = insertData.getTableName();
		List<Object> matchList = LongdbConfigHandler.getMatchingRuleListByTableName(connect, tableName);
		
		Map<String, Object> longMap = JSON.parseObject(insertData.getJsonStr(), Map.class);
		// 获取可用的数据文件
		File availableTableFile = getAvailableTableFile(
				matchList, availableTableMap, tableName, insertData.getFileName(), longMap);
		if(null == availableTableFile) {
			return false;
		}

		String fileName = availableTableFile.getName();
		insertData.setFileName(fileName);
		Integer number = updateLongdbTableRowData(fileName, tableName, status);
		StringBuffer longId = new StringBuffer(number + RandomUtil.getRandomChar(10-number.toString().length()) + fileName);
		// 设置longId的值：10位数序号 + 文件名称
		if(StringUtils.isBlank(insertData.getLongId()) || !longId.toString().equals(insertData.getLongId())) {
			longMap.put(LongDbConstant.longdbTableLongId, longId);
		}
		longMap.put(LongDbConstant.longdbTableName, tableName);
		longMap.put(LongDbConstant.longdbTableLongNumber, number);
		longMap.put(LongDbConstant.longdbTableCreateDate, System.currentTimeMillis());
		String objectLongStr = FieldHandlerUtils.parserLongMapToStr(longMap);
		if(StringUtils.isBlank(tableContentMap.get(tableName))) {
			tableContentMap.put(tableName, new StringBuffer(objectLongStr+LongDbConstant.longdbTableChangeLine));
		} else {
			tableContentMap.get(tableName).append(objectLongStr+LongDbConstant.longdbTableChangeLine);
		}
		
		long fileLength = availableTableFile.length() + insertData.getJsonStr().getBytes().length;
		if(fileLength > LongDbConfig.longTableDataMaxVolume ) {
			// 写入文件内容，清空map
			handlerFileContent(tableContentMap, availableTableMap, tableName);
			// 重新获取可用的表数据文件
			availableTableFile = getAvailableTableFile(matchList, availableTableMap, tableName, insertData.getFileName(), longMap);
			if(null == availableTableFile) {
				return false;
			}
			insertData.setFileName(availableTableFile.getName());
		}
		return true;
	}

	/**
	 * 功能描述：写入文件内容，清空map
	 * 模块作者：ZOUYONG
	 * 开发时间：2018年11月24日 上午9:34:48
	 * 更新记录：
	 * 返回数据：void
	 */
	private void handlerFileContent(Map<String, StringBuffer> tableContentMap, Map<String, File> availableTableMap, String tableName) {
		if(null == tableContentMap || tableContentMap.size() == 0) {
			return;
		}
		// 写入单个数据
		if(StringUtils.isNotBlank(tableName)) {
			// 写入文件
			LongFileOperator.writeAvailableTableFileContent(
					availableTableMap.get(tableName), tableContentMap.get(tableName).toString(), false);
			// 清空Map中数据
			tableContentMap.remove(tableName);
			availableTableMap.remove(tableName);
			return;
		}
		// 批量写入数据
		for (Iterator<String> iter = availableTableMap.keySet().iterator(); iter.hasNext(); ) {
			String key = iter.next();
			File file = availableTableMap.get(key);
			String content = tableContentMap.get(key).toString();
			// 写入文件
			LongFileOperator.writeAvailableTableFileContent(file, content, false);
			// 清空Map中数据
			tableContentMap.remove(key);
			availableTableMap.remove(key);
		}
	}
	
	/**
	 * 功能描述：获取数据文件中的有效总行数
	 × 模块作者：ZOUYONG
	 × 开发时间：2018年12月21日 下午5:13:20
	 × 返回数据：LongDataRowEntity
	 */
	private synchronized Integer updateLongdbTableRowData(String fileName, String tableName, boolean status) {
		Integer rowNumber = null;
		LongTableEntity table = null;
		if(status) {
			table = ConcurrentDataMap.dataFileNewRowCountMap.get(tableName);
			if(null == table) {
				table = connect.getCreateOperator().getLongTableByName(tableName);
			}
			rowNumber = table.getDataFileRowCountMap().get(fileName);
			rowNumber = null == rowNumber ? 1 : rowNumber+1;
			table.getDataFileRowCountMap().put(fileName, rowNumber);
			ConcurrentDataMap.dataFileNewRowCountMap.put(tableName, table);
		}
		else {
			try {
				table = connect.getCreateOperator().getLongTableByName(tableName);
				rowNumber = table.getDataFileRowCountMap().get(fileName);
				rowNumber = null == rowNumber ? 1 : rowNumber+1;
				table.getDataFileRowCountMap().put(fileName, rowNumber);
				// 更新表的配置文件内容
				connect.getCreateOperator().updateTableConfigFileContent(table, true);
			} catch(Exception e) {
				String error = "更新表的配置文件内容操作异常：" + e.getMessage();
				logger.debug(error);
				throw new RuntimeException(error);
			}
		}
		return rowNumber;
	}

	/**
	 * 功能描述：获取可用的表数据文件
	 * 模块作者：ZOUYONG
	 * 开发时间：2018年11月24日 上午8:58:43
	 * 更新记录：
	 * 返回数据：File
	 */
	public synchronized File getAvailableTableFile(List<Object> matchList, 
			Map<String, File> availableTableMap, String tableName, String fileName, Map<String, Object> longMap)
	{
		File availableTableFile = availableTableMap.get(tableName);
		if(null == availableTableFile) {
			availableTableFile = getAvailableWriteTableFile(matchList, tableName, fileName, longMap);
			availableTableMap.put(tableName, availableTableFile);
		}
		return availableTableFile;
	}

	/**
	 * 功能描述：获取指定表当前时间可用的表数据文件
	 * 模块作者：ZOUYONG
	 * 开发时间：2018年11月24日 上午8:05:40
	 * 更新记录：
	 * 返回数据：File
	 */
	public synchronized File getAvailableWriteTableFile(
			List<Object> matchList, String tableName, String fileName, Map<String, Object> longMap) {
		if(CollectionUtils.isEmpty(matchList)) {
			matchList = new ArrayList<Object>();
		}
		
		// 数据库表文件目录
		StringBuffer tableHouse = new StringBuffer();
		tableHouse
			.append(connect.getDataBaseRootDirectory() + File.separator)
			.append(connect.getDataBaseName() + File.separator)
			.append(LongDbConstant.longdbDataBaseHouse + File.separator)
			.append(tableName + File.separator + LongDbConstant.longdbTableDataHouse + File.separator);
		
		// 1-优先处理【按字段分区】逻辑
		for (Object matching : matchList) {
			if(matching instanceof MatchingField) {
				MatchingField field = (MatchingField) matching;
				List<String> fds = field.getFieldMap().get(tableName);
				if(CollectionUtils.isNotEmpty(fds)) {
					// 如果表中有多个分区字段，则依次遍历新建文件夹；因此这里要注意的是，配置文件尽量保持稳定，不要随意或经常改动配置文件
					for (String fieldName : fds) {
						Object fdObj = longMap.get(fieldName);
						if(null != fdObj) {
							if(fdObj.toString().length() > LongDbConfig.longdbFileMaxLength) {
								throw new RuntimeException("按字段["+fieldName+"]分区，字段值的长度超出"+LongDbConfig.longdbFileMaxLength+"个字符！");
							}
							tableHouse.append(fdObj.toString()).append(File.separator);
						}
					}
				}
			}
		}
		
		// 2-处理时间切片分区规则
		String[] dateAry = null;
		for (Object matching : matchList) {
			if(matching instanceof MatchingDataTime) {
				MatchingDataTime dateTime = (MatchingDataTime) matching;
				dateAry = handlerTimePartition(dateTime.getTimePartition());
				if(null != dateAry) {
					break; // 一张表只有一个时间分区配置，如果有多个则取随机序列的第一个
				}
			}
		}
		if(null == dateAry) {
			// 如果表没有设置时间分区，则取默认的时间区分规则
			dateAry = LongDbConstant.generFormatFour.format(new Date()).split("-");
		}
		StringBuffer timeNames = new StringBuffer();
		for (String str : dateAry) {
			tableHouse.append(str).append(File.separator);
			timeNames.append(str);
		}
		
		// 处理分区后，则新建文件夹操作
		File tableHouseFile = new File(tableHouse.toString());
		if(!tableHouseFile.exists()) {
			tableHouseFile.mkdirs();
		}
		File availableFile = null;
		File[] files = tableHouseFile.listFiles();
		for (File file : files) {
			if(file.length() <= LongDbConfig.longTableDataMaxVolume) {
				availableFile = file;
				break;
			}
		}
		
		if(null == availableFile) {
			int count = files.length + 1;
			if(StringUtils.isBlank(fileName)) {
				fileName = timeNames + RandomUtil.getRandomChar(10-count) + count;
			}
			availableFile = new File(tableHouseFile.getAbsolutePath() + File.separator + fileName);
			if(!availableFile.exists()) {
				try {
					availableFile.createNewFile();
				} catch (IOException e) {
					throw new RuntimeException("创建表数据文件异常", e);
				}
			}
		}
		return availableFile;
	}

	/**
	 * 功能描述：处理时间分区的逻辑
	 × 模块作者：ZOUYONG
	 × 开发时间：2019年4月15日 下午2:08:26
	 × 返回数据：Object
	 */
	private String[] handlerTimePartition(String timePartition) {
		if(StringUtils.isBlank(timePartition)) {
			return null;
		}
		SimpleDateFormat format = new SimpleDateFormat(timePartition);
		try {
			String dateTimeStr = format.format(new Date());
			return dateTimeStr.split(LongDbConstant.longdbTableDataTranE);
		} catch(Exception e) {
			throw new RuntimeException("表时间分区格式错误！", e);
		}
	}

}
